package apibasej.basic.misc;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.Map.Entry;
import java.util.logging.Level;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;

import apibasej.basic.config.ConfigManager;
import apibasej.basic.misc.io.UtilFile;
import apibasej.basic.misc.io.UtilIO;

public class UtilZip {
	
	
	//retorna o novo arquivo criado
	public static File zipInCurrentDir(File file, boolean tryDeleteOriginal) throws IOException{
		byte[] zip = zip(file);
		String fileName = file.getName();
		if(!file.isDirectory()){
			int posPonto = fileName.lastIndexOf('.');
			if(posPonto>0){
				fileName = fileName.substring(0, posPonto);
			}
		}
		fileName = fileName+".zip";
		File fileZip = new File(file.getParentFile(),fileName);
		UtilFile.writeBytesInFile(zip, fileZip);
		if(tryDeleteOriginal){
			try{
				UtilFile.deleteRecursive(file);
			}catch(Throwable t){
				ConfigManager.logDefAll(Level.WARNING,"Erro ao apagar arquivo original do ZIP: "+file.getAbsolutePath(), t);
			}
		}
		return fileZip;
	}
	
	public static byte[] zip(File file) throws IOException{
		LinkedHashMap<String,byte[]> l = new LinkedHashMap<>();
		String pathUp = file.getParentFile().getPath();
		int len = pathUp.length();
		if(!pathUp.endsWith("\\") && !pathUp.endsWith("/")){
			len++;//para o nome não começar com a barra
		}
		if(file.isDirectory()){
			for(File f : UtilFile.readFilesRecursive(file)){
				//System.out.println("zipppp>"+f.getPath().substring(len).replaceAll("\\\\", "/"));
				l.put(f.getPath().substring(len).replaceAll("\\\\", "/"), UtilFile.readFile(f));
			}
		}else{
			l.put(file.getPath().substring(len).replaceAll("\\\\", "/"), UtilFile.readFile(file));
		}
		
		/*
		for(File f : dir.listFiles()){
			l.put(dir.getName()+"/"+f.getName(), UtilFile.readFile(f));
		}*/
		return zipSeveral(l);
	}
	
	//key: nome do arquivo, cont: bytes do arquivo
	public static byte[] zipSeveral(LinkedHashMap<String,byte[]> entrys) throws IOException{
		try(ByteArrayOutputStream outBytes = new ByteArrayOutputStream();
			ZipOutputStream outZip = new ZipOutputStream(outBytes)
			){
			for(Entry<String,byte[]> e : entrys.entrySet()){
				String nameFileToZip = e.getKey();
				byte[] bytesToZip = e.getValue();
				ZipEntry entry = new ZipEntry(nameFileToZip); // Make a ZipEntry
				//entry.setSize(bytesToZip.length);//não funciona?
				outZip.putNextEntry(entry); // Store entry
				byte[] buffer = new byte[4096]; // Create a buffer for copying
				int bytesRead;
				try(ByteArrayInputStream in = new ByteArrayInputStream(bytesToZip)){ // Stream to readfile
					while ((bytesRead = in.read(buffer)) != -1) {
						outZip.write(buffer, 0, bytesRead);
					}
				}
			}
			outZip.flush();
			outZip.close();// tem que fechar antes! senão o arquivo fica inválido
			return outBytes.toByteArray();
		}
	}
	
	@SuppressWarnings("resource")// fica dando warn, por acha que não chegará no final do try por causa do while
	public static LinkedHashMap<String,byte[]> unzipSeveral(byte[] bytesZipped) throws IOException{
		LinkedHashMap<String,byte[]> resp = new LinkedHashMap<>();
		try(ByteArrayInputStream in = new ByteArrayInputStream(bytesZipped);
			ZipInputStream zipIn = new ZipInputStream(in)
			){
			ZipEntry zipEntry = zipIn.getNextEntry(); // neste momento a leitura do zipIn será desta entry
			if(zipEntry==null) {
				throw new IllegalArgumentException("Zipped bytes do not have entrys");
			}
			while(zipEntry!=null){
				if(zipEntry.getSize()>Integer.MAX_VALUE) {
					throw new IllegalArgumentException("Zip entry is larger than the size supported for an array of bytes");
				}
				String fileName = zipEntry.getName();
				byte[] bytesEntry = UtilIO.readInputStream(zipIn,true);//não pode chamar o close() no readInputStream, senão da erro no closeEntry()        //new byte[(int)zipEntry.getSize()];//new UtilIO().readInputStream(zipIn);
				byte[] b = resp.put(fileName, bytesEntry);
				if(b!=null){
					throw new IllegalArgumentException("There are two entries with the same name: "+fileName);
				}
				zipIn.closeEntry();
				zipEntry = zipIn.getNextEntry(); // neste momento a leitura do zipIn será desta entry
			}
		}
		return resp;
	}
	
	
	public static byte[] zipSingle(byte[] bytesToZip, String nameFileToZip) throws IOException {
		try(ByteArrayInputStream in = new ByteArrayInputStream(bytesToZip); // Stream to readfile
			ByteArrayOutputStream outBytes = new ByteArrayOutputStream();
			ZipOutputStream outZip = new ZipOutputStream(outBytes);
			){
			ZipEntry entry = new ZipEntry(nameFileToZip); // Make a ZipEntry
			outZip.putNextEntry(entry); // Store entry
			byte[] buffer = new byte[4096]; // Create a buffer for copying
			int bytesRead;
			while ((bytesRead = in.read(buffer)) != -1) {
				outZip.write(buffer, 0, bytesRead);
			}
			outZip.flush();
			outZip.close();// tem que fechar antes !!!!
			return outBytes.toByteArray();
		}
	}
	
	public static byte[] unzipSingle(byte[] bytesZipped) throws IOException{


		try(ByteArrayInputStream in = new ByteArrayInputStream(bytesZipped);
			ZipInputStream zipIn = new ZipInputStream(in);){
			
			ZipEntry singleEntry = zipIn.getNextEntry(); // neste momento a leitura do zipIn será desta entry
			
			if(singleEntry==null) {
				throw new IllegalArgumentException("Zipped bytes do not have entry");
			}
			if(singleEntry.getSize()>Integer.MAX_VALUE) {
				throw new IllegalArgumentException("Zip entry is larger than the size supported for an array of bytes");
			}
			
			//byte[] unzipped = new byte[(int)singleEntry.getSize()]; // singleEntry.getSize() está retornando -1
			//zipIn.read(unzipped);
		    
			byte[] resp = UtilIO.readInputStream(zipIn,true); //não pode chamar o close() no readInputStream, senão da erro no closeEntry() 
			try {
				zipIn.closeEntry();
			} catch (Throwable ioe) {
				ConfigManager.logDefAll(Level.SEVERE,ioe.getMessage(), ioe);
			}
			return resp;
		}

	}

}
